﻿using Svg;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;

namespace ZiMuGenerator
{
    public class ZiMuService
    {
        public Dictionary<char, ZiMu> ZiMuDictionary { private set; get; }

        public event EventHandler<int> InitProgressChanged;
        public event EventHandler<string> InitException;
        public event EventHandler InitFinished;

        public event EventHandler<int> SaveProgressChanged;
        public event EventHandler<string> SaveException;
        public event EventHandler SaveFinished;

        private int m_Progress;

        public bool IsInitFinished { private set; get; }
        public bool IsSaveFinished { private set; get; } = true;
        public bool IsGenerateAllPaths { set; get; } = true;
        public int WordSize { set; get; }
        public float StrokeWidth { set; get; } = 1;
        public SvgColourServer FillColor { set; get; } = new SvgColourServer(Color.Red);
        public Brush BackgroundColor { set; get; } = Brushes.Transparent;
        public SvgColourServer StrokeColor { set; get; } = new SvgColourServer(Color.Black);
        public string SaveRoot { set; get; }

        public int WordCountPerRow { set; get; } = 8;
        public int RowMargin { set; get; } = 8;
        public int ColumnMargin { set; get; } = 8;

        public void BeginInit()
        {
            Task.Run(new Action(() => { Init(); }));
        }

        private void Init()
        {
            try
            {
                ZiMuDictionary = new Dictionary<char, ZiMu>();

                var data = Config.ZiMuData;
                OnProgressChanged(true, false, 0);

                var lines = data.Split(new string[] {"\r\n"}, StringSplitOptions.None);
                OnProgressChanged(true, true, 0);

                var total = lines.Length;
                for (int i = 0; i < lines.Length; i++)
                {
                    if (ZiMu.TryParse(lines[i], out ZiMu zimu))
                    {
                        ZiMuDictionary.Add(zimu.Word, zimu);
                    }

                    OnProgressChanged(true, true, (int) ((i + 1.0) / total * 100));
                }
            }
            catch (Exception ex)
            {
                InitException?.Invoke(this, ex.Message);
            }

            IsInitFinished = true;
            InitFinished?.Invoke(this, null);
        }

        private void OnProgressChanged(bool hasReadFile, bool hasSplitLines, int readFileProgress)
        {
            var progress = hasReadFile ? 5 : 0;
            progress += hasSplitLines ? 5 : 0;
            progress += (int) (readFileProgress * 0.9);

            if (m_Progress != progress)
            {
                m_Progress = progress;
                InitProgressChanged?.Invoke(this, m_Progress);
            }
        }

        /// <summary>
        /// 字符校验，检测要生成的文本是否都支持生成字帖
        /// </summary>
        /// <param name="text"></param>
        /// <param name="invalidCharacterCount"></param>
        /// <param name="invalidCharacters"></param>
        /// <returns>校验通过返回<see langword="true"/>，失败返回<see langword="false"/>，
        /// 当失败时，参数<paramref name="invalidCharacters"/>和<paramref name="invalidCharacterCount"/>将返回相应信息</returns>
        public bool CharactersVerify(string text, out int invalidCharacterCount, out string invalidCharacters)
        {
            invalidCharacterCount = 0;
            invalidCharacters = string.Empty;

            if (text == null)
            {
                return true;
            }

            var invalidCharacterSet = new HashSet<char>();
            var invalidCharacterList = new List<Tuple<int, char>>(); // 元组：<位置，字符>
            for (int i = 0; i < text.Length; i++)
            {
                if (!ZiMuDictionary.ContainsKey(text[i]))
                {
                    if (!invalidCharacterSet.Contains(text[i]))
                    {
                        invalidCharacterSet.Add(text[i]);
                        invalidCharacterList.Add(new Tuple<int, char>(i, text[i]));
                    }
                }
            }

            if (invalidCharacterSet.Count == 0)
            {
                return true;
            }

            // 生成相应提示
            invalidCharacterCount = invalidCharacterSet.Count;

            var invalidCharactersBuilder = new StringBuilder();
            for (int i = 0; i < Math.Min(invalidCharacterList.Count, 20); i++) // 最大提示20个无效字符
            {
                var wordIndex = invalidCharacterList[i].Item1 + 1; // 位置从1开始，更人性化些
                var word = invalidCharacterList[i].Item2;

                if (i == 0)
                {
                    invalidCharactersBuilder.Append($"({wordIndex},'{word}')");
                }
                else
                {
                    invalidCharactersBuilder.Append($", ({wordIndex},'{word}')");
                }
            }

            if (invalidCharacterList.Count > 20)
            {
                invalidCharactersBuilder.Append("...");
            }

            invalidCharacters = invalidCharactersBuilder.ToString();

            return false;
        }

        /// <summary>
        /// 过滤掉无效字符
        /// </summary>
        public string FilterOutInvalidCharacters(string text)
        {
            if (text == null)
            {
                return string.Empty;
            }

            var result = new StringBuilder();
            for (int i = 0; i < text.Length; i++)
            {
                if (ZiMuDictionary.ContainsKey(text[i]))
                {
                    result.Append(text[i]);
                }
            }

            return result.ToString();
        }

        public void ParseAndSave(string text)
        {
            if (text == null || text.Length == 0 || ZiMuDictionary == null || ZiMuDictionary.Count == 0)
            {
                return;
            }

            Task.Run(new Action(() =>
            {
                IsSaveFinished = false;
                SaveProgressChanged?.Invoke(this, 0);

                try
                {
                    for (int i = 0; i < text.Length; i++)
                    {
                        if (!ZiMuDictionary.ContainsKey(text[i]))
                        {
                            continue;
                        }

                        var zimu = ZiMuDictionary[text[i]];
                        zimu.FillColor = FillColor;
                        zimu.BackgroundColor = BackgroundColor;
                        zimu.StrokeColor = StrokeColor;
                        zimu.StrokeWidth = StrokeWidth;

                        Bitmap[] bitmaps;
                        if (IsGenerateAllPaths)
                        {
                            bitmaps = zimu.GetBitmaps(WordSize);
                        }
                        else
                        {
                            bitmaps = new Bitmap[] {zimu.GetBitmap(WordSize)};
                        }

                        for (int j = 0; j < bitmaps.Length; j++)
                        {
                            var path =
                                $"{SaveRoot}\\[{(i + 1).ToString().PadLeft(4, '0')}]{text[i]}_{(j + 1).ToString().PadLeft(2, '0')}.png";
                            bitmaps[j].Save(path);
                        }

                        SaveProgressChanged?.Invoke(this, (int) ((i + 1.0) / text.Length * 100.0));
                    }
                }
                catch (Exception ex)
                {
                    SaveException?.Invoke(this, ex.Message);
                }

                IsSaveFinished = true;
                SaveFinished?.Invoke(this, null);
            }));
        }

        public Bitmap GetPreviewImage(char word)
        {
            if (!IsInitFinished)
            {
                return null;
            }

            if (!ZiMuDictionary.ContainsKey(word))
            {
                return null;
            }

            var zimu = ZiMuDictionary[word];
            zimu.FillColor = FillColor;
            zimu.BackgroundColor = BackgroundColor;
            zimu.StrokeColor = StrokeColor;
            zimu.StrokeWidth = StrokeWidth;

            Bitmap[] bitmaps;
            if (IsGenerateAllPaths)
            {
                bitmaps = zimu.GetBitmaps(WordSize);
            }
            else
            {
                bitmaps = new Bitmap[] {zimu.GetBitmap(WordSize)};
            }

            var width = WordSize * bitmaps.Length;
            var bmp = new Bitmap(width, bitmaps[0].Height);
            using (var g = Graphics.FromImage(bmp))
            {
                for (int i = 0; i < bitmaps.Length; i++)
                {
                    g.DrawImage(bitmaps[i], new PointF(i * WordSize, 0));
                }
            }

            return bmp;
        }

        public Bitmap GetFullPreviewImage(string text, bool isContinuity)
        {
            if (ZiMuDictionary == null || ZiMuDictionary.Count == 0)
            {
                return null;
            }

            var allBitmaps = GetAllBitmaps(text);
            if (allBitmaps == null)
            {
                return null;
            }

            return isContinuity ? DrawContinuity(allBitmaps) : DrawDispersed(allBitmaps);
        }

        private Bitmap DrawContinuity(List<Bitmap[]> rowBitmaps)
        {
            var allBitmaps =
                (from row in rowBitmaps
                    from bitmap in row
                    select bitmap).ToArray();
            int width = 0;

            var maxRowWords = allBitmaps.Length;
            if (maxRowWords >= WordCountPerRow)
            {
                width = WordCountPerRow * WordSize + (WordCountPerRow - 1) * ColumnMargin;
            }
            else
            {
                width = maxRowWords * WordSize + (maxRowWords - 1) * ColumnMargin;
            }

            var rowCount = (int) Math.Ceiling(maxRowWords / (double) WordCountPerRow);

            var result = new Bitmap(width, rowCount * WordSize + (rowCount - 1) * RowMargin);

            using (var g = Graphics.FromImage(result))
            {
                var currentRow = 0;
                var currentColumn = 0;

                foreach (var bmp in allBitmaps)
                {
                    g.DrawImage(bmp,
                        new PointF(currentColumn * (WordSize + ColumnMargin), currentRow * (WordSize + RowMargin)));

                    currentColumn++;
                    if (currentColumn >= WordCountPerRow)
                    {
                        currentRow++;
                        currentColumn = 0;
                    }
                }
            }

            return result;
        }

        private Bitmap DrawDispersed(List<Bitmap[]> allBitmaps)
        {
            int width = 0;

            var maxRowWords = (from row in allBitmaps
                select row.Length).Max();
            if (maxRowWords >= WordCountPerRow)
            {
                width = WordCountPerRow * WordSize + (WordCountPerRow - 1) * ColumnMargin;
            }
            else
            {
                width = maxRowWords * WordSize + (maxRowWords - 1) * ColumnMargin;
            }

            var rowCount = (int) Math.Ceiling(allBitmaps.Sum(row => row.Length) / (double) WordCountPerRow);

            var result = new Bitmap(width, rowCount * WordSize + (rowCount - 1) * RowMargin);

            using (var g = Graphics.FromImage(result))
            {
                var currentRow = 0;
                var currentColumn = 0;

                foreach (var row in allBitmaps)
                {
                    for (var i = 0; i < row.Length; i++)
                    {
                        g.DrawImage(row[i],
                            new PointF(currentColumn * (WordSize + ColumnMargin), currentRow * (WordSize + RowMargin)));

                        currentColumn++;
                        if (currentColumn >= WordCountPerRow)
                        {
                            currentColumn = 0;
                            if (i != row.Length - 1)
                            {
                                currentRow++;
                            }
                        }
                    }
                    
                    currentColumn = 0;
                    currentRow++;
                }
            }

            return result;
        }


        private List<Bitmap[]> GetAllBitmaps(string text)
        {
            var result = new List<Bitmap[]>();
            var words = from word in text where ZiMuDictionary.ContainsKey(word) select ZiMuDictionary[word];
            foreach (var zimu in words)
            {
                zimu.FillColor = FillColor;
                zimu.BackgroundColor = BackgroundColor;
                zimu.StrokeColor = StrokeColor;
                zimu.StrokeWidth = StrokeWidth;
                result.Add(zimu.GetBitmaps(WordSize));
            }

            return result;
        }
    }
}